/*
 * $Id: Client.java 23 2002-09-29 13:59:30Z bpruessmann $
 * 
 * Copyright (c) 2001 Boris Pruessmann and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 *    Boris Pruessmann - Initial implementation.
 * 	  David Corbin - added revert, delete
 */
package net.sourceforge.p4eclipse.core.api;

import java.text.MessageFormat;

import net.sourceforge.p4eclipse.core.PerforceException;
import net.sourceforge.p4eclipse.core.Policy;
import net.sourceforge.p4eclipse.core.resources.RemoteFile;
import net.sourceforge.p4eclipse.core.syncinfo.FolderSyncInfo;
import net.sourceforge.p4eclipse.core.syncinfo.ResourceSyncInfo;
import net.sourceforge.p4eclipse.internal.core.api.Change;
import net.sourceforge.p4eclipse.internal.core.api.Changes;
import net.sourceforge.p4eclipse.internal.core.api.Counters;
import net.sourceforge.p4eclipse.internal.core.api.Describe;
import net.sourceforge.p4eclipse.internal.core.api.Diff;
import net.sourceforge.p4eclipse.internal.core.api.Dirs;
import net.sourceforge.p4eclipse.internal.core.api.Filelog;
import net.sourceforge.p4eclipse.internal.core.api.Fstat;
import net.sourceforge.p4eclipse.internal.core.api.Info;
import net.sourceforge.p4eclipse.internal.core.api.Opened;
import net.sourceforge.p4eclipse.internal.core.api.Reopen;
import net.sourceforge.p4eclipse.internal.core.api.Resolve;
import net.sourceforge.p4eclipse.internal.core.api.Resolved;
import net.sourceforge.p4eclipse.internal.core.api.SimpleCmd;
import net.sourceforge.p4eclipse.internal.core.api.Submit;
import net.sourceforge.p4eclipse.internal.core.api.Where;
import net.sourceforge.p4eclipse.ui.PerforceUIPlugin;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;


/**
  * @version	$Revision: 23 $
  * @author 	<a href="mailto:bpruessmann@users.sourceforge.net">Boris Pruessmann</a>
  * @author		<a href="mailto:dcorbin@users.sourceforge.net">David Corbin</a>
  */
public class Client implements IClient
{
	public static final String SERVER_SEPARATOR = "/"; //$NON-NLS-1$
	
	private static boolean isAvailable;
	
	/* DON'T TOUCH THE CONTENT OF THIS VARIABLE */
	private int _nativeHandle;

	private PerforceError error;	
	private IInfoResult perforceInfoResult;
	
	{
		try
		{
			if (!isAvailable)
			{
				System.loadLibrary("perforce"); //$NON-NLS-1$
				isAvailable = true;
			}
		}
		catch (UnsatisfiedLinkError e)
		{
			isAvailable = false;
		}
	}
	private void simpleCmd(String[] fileNames, String cmd) throws PerforceException
	{
		SimpleCmd clientUser = SimpleCmd.newInstance(cmd); 
		run(fileNames, clientUser); 
		
		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
			throw new PerforceException(clientUser.getStatus());		
	}

	public static IClient newInstance()
	{
		return new Client();
	}

	/**
	  * Creates a new Client instance.
	  */
	Client()
	{
		Policy.assertTrue(isAvailable);			
		construct();
	}
	

	/**
	  * @see Object#finalize()
	  */
	public void finalize() throws Throwable
	{
		super.finalize();
		destruct();
	}

	public void init()
	{
		error = new PerforceError();
		error.init();
		
		init(error);
	}


	public int dispose()
	{
		int result = dispose(error);
		
		error.dispose();
		error = null;
		
		return result;
	}


	/*
	 * Wrapper for ClientApi::Init.
	 */
	private native void init(PerforceError error);

	/*
	 * Wrapper for ClientApi::Dropped.
	 */
	public native boolean dropped();	
	
	/**
	  * Wrapper for ClientApi::SetArgv.
	  */
	public native void setArguments(String[] arguments);
	
	
	/*
	 * Wrapper for ClientApi::SetPort.
	 */
	public native void setPerforcePort(String serverNameAndPort);
	
	
	/**
	  * Wrapper for ClientApi::Run.
	  */
	public native void run(String function, IClientUser clientUser);
	
	/**
	  * Wrapper for ClientApi::SetProtocol.
	  */
	public native void setProtocol(String key, String value);
	
	/*
	 * Wrapper for ClientApi::Final.
	 */
	private native int dispose(PerforceError error);

	/*
	 * Called by the constructor to initialize the native side
	 * of this class.
	 */
	private native void construct();
	
	/*
	 * Called by finalize() to destroy the native side of this
	 * class.
	 */
	private native void destruct();

	/*
	 * Wrapper for ClientApi::GetClient.
	 */
	public native String getPerforceClient();

	/*
	 * Wrapper for ClientApi::GetPort.
	 */
	public native String getPerforcePort();

	/*
	 * Wrapper for ClientApi::GetUser.
	 */
	public native String getPerforceUser();

	/*
	 * Wrapper for ClientApi::SetClient.
	 */
	public native void setPerforceClient(String client);

	/*
	 * Wrapper for ClientApi::SetUser.
	 */
	public native void setPerforceUser(String user);
	
	public PerforceError getPerforceError()
	{
		return error;
	}
	
	// ***** HIGH LEVEL API STARTS HERE ****

	/**
	  * @see IClient#run(IPerforceCommand)
	  */
	public synchronized void run(String[] arguments, IPerforceCommand command)
	{
		long start = System.currentTimeMillis();
		
		setArguments(arguments);
		run(command.getCommand(), command);
		
		long stop = System.currentTimeMillis();
		if (PerforceUIPlugin.getPlugin().isDebugging())
			System.out.println(MessageFormat.format("Client.{0}() took {1}ms.", new Object[]{ command.getCommand(), new Long(stop-start) }));
	}

	/** @see IClient#change(String) */
	public void change(String changeSpec)
		throws PerforceException
	{
		Change clientUser = Change.newInstanceForOutput(changeSpec);
		run(new String[]{ "-i" }, clientUser); //$NON-NLS-1$

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
			throw new PerforceException(clientUser.getStatus());		
	}

	/** @see IClient#change(Integer) */
	public IChangeResult change(Integer changelist)
		throws PerforceException
	{
		Change clientUser = Change.newInstanceForInput();
		
		String[] arguments = changelist != null && changelist.intValue() != -1 ?
		                     new String[]{ "-o", changelist.toString() } : new String[]{ "-o" }; //$NON-NLS-1$ //$NON-NLS-2$
		run(arguments, clientUser); 

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
			throw new PerforceException(clientUser.getStatus());	
			
		return clientUser.getResult();	
	}

	/** @see IClient#changes(String) */
	public IChangesResult[] changes(String status, int count) 
		throws PerforceException
	{
		Policy.assertTrue("pending".equals(status) || "submitted".equals(status)); //$NON-NLS-1$ //$NON-NLS-2$
	
		String[] arguments = (count == Integer.MAX_VALUE) ?
		                     new String[] { "-l", "-s", status } : //$NON-NLS-1$ //$NON-NLS-2$
		                     new String[] { "-l", "-m", Integer.toString(count), "-s", status }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		
		Changes clientUser = Changes.newInstance();
		run(arguments, clientUser); 
		
		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
			throw new PerforceException(clientUser.getStatus());
			
		return clientUser.getResult();
	}

	/** @see IClient#counters() */
	public ICountersResult counters() 
		throws PerforceException
	{
		Counters clientUser = Counters.newInstance();
		run(new String[0], clientUser);

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
			throw new PerforceException(clientUser.getStatus());
			
		return clientUser.getResult();
	}

	/** @see IClient#describe(Integer) */
	public IDescribeResult describe(Integer changelist)
		throws PerforceException
	{
		Describe clientUser = Describe.newInstance();
		run(new String[] { changelist.toString() }, clientUser);
		
		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
			throw new PerforceException(clientUser.getStatus());
			
		return clientUser.getResult();
	}

	/** @see IClient#dirs(String) */
	public FolderSyncInfo[] dirs(String dir)
		throws PerforceException
	{
		Dirs clientUser = Dirs.newInstance();
		run(new String[] { "-C", dir }, clientUser); //$NON-NLS-1$

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
			throw new PerforceException(clientUser.getStatus());
			
		return clientUser.getResult();
	}

	/** @see IClient#fstat(String) */
	public ResourceSyncInfo[] fstat(String file) 
		throws PerforceException
	{
		Fstat clientUser = Fstat.newInstance();
		run(new String[] { "-C", file }, clientUser); //$NON-NLS-1$

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
			throw new PerforceException(clientUser.getStatus());

		return clientUser.getResult();
	}

	/** @see IClient#info() */
	public synchronized IInfoResult info()
	{
		if (perforceInfoResult == null)
		{
			Info clientUser = Info.newInstance();
			run(new String[0], clientUser);
			
			perforceInfoResult = clientUser.getResult();
		}
		
		return perforceInfoResult;
	}

	/** @see IClient#filelog() */
	public ILogEntry[] filelog(RemoteFile remoteFile, int maxRevs, boolean follow)
		throws PerforceException
	{
		String[] arguments;
		if (follow)
		{
			arguments = new String[]
			{ 
				"-i", "-l", "-m", 
				Integer.toString(maxRevs), 
				remoteFile.getDepotFilename()
			};
		}
		else
		{
			arguments = new String[]
			{ 
				"-l", "-m", 
				Integer.toString(maxRevs), 
				remoteFile.getDepotFilename() 
			};
		}
		
		Filelog filelogUser = 
			Filelog.newInstance(remoteFile, new NullProgressMonitor());
		run(arguments, filelogUser);

		if (filelogUser.getStatus().getSeverity() == IStatus.ERROR)
			throw new PerforceException(filelogUser.getStatus());

		return filelogUser.getResult();
	}

	/** @see IClient#opened(Integer) */
	public IOpenedResult[] opened(Integer changelist) 
		throws PerforceException
	{
		String changelistString = changelist.intValue() == -1 ? 
		                          "default" : changelist.toString(); //$NON-NLS-1$
		
		Opened clientUser = Opened.newInstance();
		run(new String[] { "-c", changelistString }, clientUser); //$NON-NLS-1$

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
			throw new PerforceException(clientUser.getStatus());

		return clientUser.getResult();
	}

	/** @see IClient#opened(boolean) */
	public IOpenedResult[] opened(boolean allClients) 
		throws PerforceException
	{
		String[] arguments = allClients ?
		                     new String[] { "-a" } : new String[0]; //$NON-NLS-1$
		
		Opened clientUser = Opened.newInstance();
		run(arguments, clientUser); 

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
			throw new PerforceException(clientUser.getStatus());

		return clientUser.getResult();
	}

	/** @see IClient#reopen(Integer, String[]) */
	public void reopen(Integer changelist, String[] filenames)
		throws PerforceException
	{
		Reopen clientUser = Reopen.newInstance();
		String[] arguments = new String[filenames.length + 2];
		arguments[0] = "-c"; //$NON-NLS-1$
		arguments[1] = changelist.intValue() == -1 ? 
		               "default" : changelist.toString(); //$NON-NLS-1$
		
		System.arraycopy(filenames, 0, arguments, 2, filenames.length);
		
		run(arguments, clientUser);
		
		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
			throw new PerforceException(clientUser.getStatus());		
	}

	public IResolveResult[] resolve(String spec, boolean simulate)
		throws PerforceException
	{
		Resolve clientUser = Resolve.newInstance();
		
		String[] arguments;
		if (simulate)
			arguments = new String[] { "-n", spec };
		else
			arguments = new String[] { spec };
			
		run(arguments, clientUser);

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
			throw new PerforceException(clientUser.getStatus());
			
		return clientUser.getResult();
	}
	
	/** @see IClient#resolved(String) */
	public String resolved(String filename) 
		throws PerforceException 
	{
		Resolved clientUser = Resolved.newInstance();
		run(new String[] { filename }, clientUser);

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
			throw new PerforceException(clientUser.getStatus());
			
		return clientUser.getResult();		
	}

	/** @see IClient#submit(String) */
	public String[] submit(String changeSpec)
		throws PerforceException
	{
		Submit clientUser = Submit.newInstanceForOutput(changeSpec);
		run(new String[]{ "-i" }, clientUser); //$NON-NLS-1$

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
			throw new PerforceException(clientUser.getStatus());		
			
		return clientUser.getAffectedFiles();
	}
	

	/** @see IClient#revert(String[]) */
	public void revert(String [] fileNames)
		throws PerforceException
	{
		simpleCmd(fileNames,"revert"); //$NON-NLS-1$
	}
	/** @see IClient#delete(String[]) */
	public void delete(String [] fileNames)
		throws PerforceException
	{
		simpleCmd(fileNames,"delete"); //$NON-NLS-1$
	}
	/** @see IClient#integrate(String[]) */
	public void integrate(String from, String to)
		throws PerforceException
	{
		simpleCmd(new String[] { from, to },"integrate");  //$NON-NLS-1$
	}
	/** @see IClient#edit(String[]) */
	public void edit(String [] fileNames)
		throws PerforceException
	{
		simpleCmd(fileNames, "edit");		//$NON-NLS-1$
	}

	
	public void add(String[] fileNames)
		throws PerforceException
	{
		simpleCmd(fileNames,"add");
	}

	public void sync(String syncSpecification)
		throws PerforceException
	{
		simpleCmd(new String[] {syncSpecification},"sync");
	}

	/**
	  * @see net.sourceforge.p4eclipse.core.api.IClient#diff(String[])
	  */
	public String[] diff(String[] files) 
		throws PerforceException 
	{
		if (files.length == 0)
		{
			return new String[0];
		}
		
		IWhereResult[] whereResults = where(files);
		for (int i = 0, length = files.length; i < length; i++)
		{
			files[i] = whereResults[i].getClientFilename();
		}

		Diff clientUser = Diff.newInstance();	
		String[] arguments = new String[files.length + 1];
		arguments[0] = "-sr";
		System.arraycopy(files, 0, arguments, 1, files.length);
		
		run(arguments, clientUser);

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
			throw new PerforceException(clientUser.getStatus());
		
		return clientUser.getResult();
	}


	/** @see IClient#where(String[]) */
	public IWhereResult where(String filename)
		throws PerforceException
	{
		return where(new String[] { filename })[0];
	}

	/** @see IClient#where(String[]) */
	public IWhereResult[] where(String[] filenames)
		throws PerforceException
	{
		IInfoResult infoResult = info();
		if (infoResult.getClientRoot() == null)
			throw new RuntimeException("Invalid client name."); 
		
		Where clientUser = Where.newInstance(infoResult.getClientRoot());
		run(filenames, clientUser);
	
		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
			throw new PerforceException(clientUser.getStatus());
				
		return clientUser.getResult();
	}
}

